
package tcg.serialization;

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import de.justphil.tcg.tcgserver.commons.config.GridConfig;
import de.justphil.tcg.tcgserver.commons.grid.Grid;
import de.justphil.tcg.tcgserver.game.mechanics.example.DeckCreator;
import de.justphil.tcg.tcgserver.game.mechanics.game.Game;
import de.justphil.tcg.tcgserver.game.mechanics.game.OnGameProgressListener;
import de.justphil.tcg.tcgserver.game.mechanics.game.OnUserInteractionListener;
import de.justphil.tcg.tcgserver.game.mechanics.gui.Board;
import de.justphil.tcg.tcgserver.game.mechanics.monster.MonsterList;
import de.justphil.tcg.tcgserver.game.mechanics.util.tools.simulation.UserSimulator;
import org.slf4j.Logger;

import com.hazelcast.core.Hazelcast;
import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.*;

public class GameSimulationTest implements OnGameProgressListener, OnUserInteractionListener {
	
	private static final Logger				log						=
																			gameLogger(GameSimulationTest.class);
	private static final int				RUNS					= 10000;
	private static final int				START_SEED				= 1;
	private static final int				WAIT_BETWEEN_GAMES_MS	= 0;
	/* -1 for no automatic selection */
	private static int						SELECTION_TIME_MS		= 0;
	/* -1 for no automatic confirmation */
	private static int						CONFIRM_TIME_MS			= 0;
	private static final boolean			USE_SERIALIZATION		= true;
	private final boolean					USE_GUI					= WAIT_BETWEEN_GAMES_MS >= 100;
	private final boolean					USE_SIMULATOR			= SELECTION_TIME_MS >= 0
																			|| CONFIRM_TIME_MS >= 0;
	private ConcurrentMap<Integer, Game>	gamesMap;
	private volatile boolean				gameOver;
	private volatile boolean				selectionRequired;
	private volatile int					selectionPlayer;
	private volatile int					selectionSlot;
	private volatile boolean				resumeRequired;
	private volatile boolean				selected;
	private volatile int					selectedSlot;
	private volatile int					selectedPlayer;
	private volatile int					winner;
	private volatile boolean				confirmed;
	private UserSimulator					simulator;
	private Game							game;
	private Board							board;
	private final Lock						lock;
	
	public GameSimulationTest() {
	
		lock = new ReentrantLock();
		game = null;
		board = null;
		simulator = null;
	}
	
	public static void main(String[] args) {
	
		GameSimulationTest test = new GameSimulationTest();
		test.simulate();
	}
	
	public void simulate() {
	
		if (USE_SERIALIZATION) {
			initSerialization();
		}
		// creation of Game object
		for (int seed = START_SEED; seed < RUNS + START_SEED; seed++) {
			if (USE_GUI) {
				initBoard();
			}
			if (USE_SIMULATOR) {
				initSimulator(seed);
			}
			initGame(seed);
			onResumeRequired(-1);
			while (!gameOver) {
				
				synchronized (this) {
					while (!gameOver && !selectionRequired && !resumeRequired && !selected
							&& !confirmed) {
						try {
							this.wait();
						}
						catch (InterruptedException ignored) {
						}
					}
				}
				
				if (selectionRequired) {
					handleSelectionRequired();
				}
				else if (resumeRequired) {
					handleResumeRequired();
				}
				else if (selected) {
					handleSelection();
				}
				else if (confirmed) {
					handleResume();
				}
			}
			handleGameOver();
			try {
				Thread.sleep(WAIT_BETWEEN_GAMES_MS);
			}
			catch (InterruptedException ignored) {
			}
		}
		if (USE_SERIALIZATION) {
			finishSerialization();
		}
	}
	
	@Override
	public void onGameOver(int winner) {
	
		gameOver = true;
		this.winner = winner;
		synchronized (this) {
			this.notifyAll();
		}
	}
	
	@Override
	public void onProgress(int player, int slot, long cardId, int[] propertyTypes, int life,
			int attack, int defense, int agility) {
	
		if (USE_GUI) {
			board.onProgress(player, slot, cardId, propertyTypes, life, attack, defense, agility);
		}
		if (USE_SIMULATOR) {
			simulator.onProgress(player, slot, cardId, propertyTypes, life, attack, defense,
					agility);
		}
	}
	
	@Override
	public void onGameInfo(int type, String text) {
	
	}
	
	@Override
	public void onSelectionRequired(int player, int slot) {
	
		selectionRequired = true;
		selectionPlayer = player;
		selectionSlot = slot;
		synchronized (this) {
			this.notifyAll();
		}
	}
	
	@Override
	public void onResumeRequired(int player) {
	
		resumeRequired = true;
		synchronized (this) {
			this.notifyAll();
		}
	}
	
	// ##############################################################################
	@Override
	public void onSlotSelected(int player, int slot) {
	
		this.selectedSlot = slot;
		this.selectedPlayer = player;
		this.selected = true;
		synchronized (this) {
			this.notifyAll();
		}
	}
	
	@Override
	public void onResume() {
	
		this.confirmed = true;
		synchronized (this) {
			this.notifyAll();
		}
	}
	
	private void handleResume() {
	
		confirmed = false;
		lock.lock();
		game.onResume();
		lock.unlock();
	}
	
	private void handleSelection() {
	
		selected = false;
		lock.lock();
		game.onSlotSelected(selectedPlayer, selectedSlot);
		lock.unlock();
	}
	
	private void handleSelectionRequired() {
	
		selectionRequired = false;
		assert selected == false;
		if (USE_SERIALIZATION) {
			serialize();
			deserialize();
		}
		if (USE_SIMULATOR) {
			simulator.onSelectionRequired(selectionPlayer, selectionSlot);
			synchronized (simulator) {
				simulator.notifyAll();
			}
		}
		if (USE_GUI) {
			board.onSelectionRequired(selectionPlayer, selectionSlot);
		}
	}
	
	private void handleResumeRequired() {
	
		resumeRequired = false;
		if (USE_SERIALIZATION) {
			serialize();
			deserialize();
		}
		if (USE_SIMULATOR) {
			simulator.onResumeRequired(-1);
			synchronized (simulator) {
				simulator.notifyAll();
			}
		}
	}
	
	private void handleGameOver() {
	
		gameOver = false;
		if (USE_SIMULATOR) {
			simulator.onGameOver(winner);
			synchronized (simulator) {
				simulator.notifyAll();
			}
			try {
				simulator.join();
			}catch(InterruptedException ignored){
				
			}
		}
		if (USE_GUI) {
			board.onGameOver(winner);
		}
		log.info("# Game Over.", new Object[] {});
	}
	
	private void initGame(long seed) {
	
		DeckCreator deckCreator = new DeckCreator();
		MonsterList[] decks = new MonsterList[] {
				deckCreator.getRandomDeck(seed), deckCreator.getRandomDeck(Long.MAX_VALUE - seed) };
		game = new Game(seed, decks);
		game.addOnGameProgressListener(this, true);
	}
	
	private void initSimulator(long seed) {
	
		log.info("# Starting UserSimulalator with selection time {} and confirm time {}.",
				new Object[] {
						SELECTION_TIME_MS, CONFIRM_TIME_MS });
		simulator = new UserSimulator(seed, SELECTION_TIME_MS, CONFIRM_TIME_MS);
		simulator.setOnUserInteractionListener(this);
		simulator.start();
	}
	
	private void initBoard() {
	
		/* Killing the previously shown GUI */
		if (board != null) {
			board.exit();
		}
		log.info("# Starting Board.", new Object[] {});
		board = new Board(game);
		board.addOnUserInteractionListener(this);
	}
	
	private void serialize() {
	
		log.info("# Serializing.", new Object[] {});
		gamesMap.put(1, game);
		game = null;
	}
	
	private void deserialize() {
	
		log.info("# Deserializing.", new Object[] {});
		game = gamesMap.get(1);
		game.addOnGameProgressListener(this, false);
	}
	
	private void initSerialization() {
	
		Grid.initWebSocketServerGrid();
		gamesMap = Hazelcast.getMap(GridConfig.GRID_TEST_GAMES);
	}
	
	private void finishSerialization() {
	
		gamesMap = null;
		Grid.shutdownAll();
	}
}
